﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.ComponentModel;

namespace LEVNet
{
    namespace LEVNetSocket
    {
        /// <summary>
        /// ref example from http://msdn.microsoft.com/zh-tw/library/fx6588te(v=vs.110).aspx
        /// </summary>
        public class ServerSocket : IDisposable
        {
            //public const string _OldVer_EOF_Token = "<EOF>";
            //public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;


            //public event EventHandler SocketMsgEventHandler;
            //public delegate void (SocketMsgEventHandlerLEVSocketEventType eventTyp, Socket client, string Msg);
            //SocketMsgEventHandler msgEventHandler;
            public delegate void SocketExceptionEventHandler(LEVSocketExceptionType eventTyp, Socket client, string Msg);
            SocketExceptionEventHandler excptEventHandler;
            public delegate void ReceivingEventHandler(SocketReceivingTyp receiveType, Socket client, byte[] receivingData);
            ReceivingEventHandler receiveEventHandler;

            //默認信號為未發送狀態
            private static ManualResetEvent SignalEvent = new ManualResetEvent(false);

            private BackgroundWorker bgwServer;
            private IPEndPoint gLocalEndPoint;
            private Socket gListenerSocket;

            //參數會指定可在佇列中等候接收的輸入連接數。若要決定可指定的最大連接數，除非同時間的連線非常的大，否則值4應該很夠用。
            private int gMaxListenIncomingConnections;

            private bool gListenerAcceptLoopFlag = false;

            //private List<byte[]> gSocketCmdTokenBArray;
            //private byte[] gOldVer_EOF_TokenBArray;
            private List<Socket> gClientSocketList;


            public ServerSocket(int connectPortNumber, int maxListenIncomingConnections)
            {
                //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
                gLocalEndPoint = new IPEndPoint(IPAddress.Any, connectPortNumber);
                gMaxListenIncomingConnections = maxListenIncomingConnections;
                initServerSocket();
            }
            public ServerSocket(string ip, int connectPortNumber, int maxListenIncomingConnections)
            {
                //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
                gLocalEndPoint = new IPEndPoint(IPAddress.Parse(ip), connectPortNumber);
                gMaxListenIncomingConnections = maxListenIncomingConnections;
                initServerSocket();
            }
            public ServerSocket(IPEndPoint localEndPoint, int maxListenIncomingConnections)
            {
                //建立本機監聽位址及埠號，IPAddress.Any表示監聽所有的介面。
                gLocalEndPoint = localEndPoint;
                gMaxListenIncomingConnections = maxListenIncomingConnections;
                initServerSocket();

            }
            private void initServerSocket()
            {
                gClientSocketList = new List<Socket>();
                bgwServer = new BackgroundWorker();
                bgwServer.WorkerReportsProgress = true;
                bgwServer.WorkerSupportsCancellation = true;
                bgwServer.DoWork += new DoWorkEventHandler(bgwServerDoWork);
                bgwServer.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgwServerRunWorkerCompleted);
            }
            /// <summary>
            /// Implement IDisposable.
            /// </summary>
            public void Dispose()
            {
                try
                {
                    gListenerAcceptLoopFlag = false;
                    //gListenerSocket.Shutdown(SocketShutdown.Both);
                    gListenerSocket.Close();
                }
                catch
                {
                }
                //GC.SuppressFinalize(this);
            }
            public bool isServerSocketWorking()
            {
                return bgwServer.IsBusy;
            }
            public void Start()
            {
                if (!bgwServer.IsBusy)
                {
                    bgwServer.RunWorkerAsync();
                }
                //else
                //{
                //    FireExceptionEvent(LEVSocketExceptionType.ReConnectException, null, "Server was already started for Listening.");
                //}
            }
            public void Stop()
            {
                gListenerAcceptLoopFlag = false;
                //SignalEvent.Set();
                //bgwServer.CancelAsync();
                foreach (Socket s in gClientSocketList)
                {
                    try
                    {
                        FireSocketMsgEvent(LEVSocketEventType.ClientDisconnect, s, "Client is going to be disconnected.");
                        closeClientSocket(s, false, true);
                    }
                    catch (Exception e)
                    {
                        FireExceptionEvent(LEVSocketExceptionType.DisConnectException, s, "Client being disconnected Failure." + Environment.NewLine + e);
                    }
                }
                gClientSocketList.Clear();

                //gListenerSocket.Disconnect(false);
                //gListenerSocket.Shutdown(SocketShutdown.Both);
                gListenerSocket.Close();
            }
            //public Socket GetServerHandler()
            //{
            //    return gListenerSocket;
            //}
            public List<Socket> GetAllConnectedClientHandler()
            {
                return gClientSocketList;
            }
            private void closeClientSocket(Socket clientHandler, bool removeFromList, bool closeMsgSendOut)
            {
                if (removeFromList)
                {
                    gClientSocketList.Remove(clientHandler);
                }
                if (closeMsgSendOut)
                {
                    Send_PreviousVer(clientHandler, "Server is going to close the connection.");
                }
                clientHandler.Disconnect(false);
                clientHandler.Shutdown(SocketShutdown.Both);
                clientHandler.Close();
            }
            private void bgwServerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                //這裡是程式完成時會觸發的事件，
                //這是 background執行緒結束後會觸發的值
                //SocketInformLog("bgwServerRunWorkerCompleted." + Environment.NewLine);
            }
            private void bgwServerDoWork(object sender, DoWorkEventArgs e)
            {
                StartListening();
                FireSocketMsgEvent(LEVSocketEventType.ServerStop, null, "Server has been stoped");
                //SocketInformLog("Server has been stoped Listening." + Environment.NewLine);
            }
            private void StartListening()
            {
                // 實體化 ListenerSocket
                // Create a TCP/IP socket.
                gListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);


                //使用Thread
                //new Thread(() => StartListening()).Start();

                // Bind socket to endpoint and listen for incoming connections.
                try
                {
                    //socket連繫到該位址
                    gListenerSocket.Bind(gLocalEndPoint);

                    // 啟動監聽
                    //backlog=4 參數會指定可在佇列中等候接收的輸入連接數。若要決定可指定的最大連接數，除非同時間的連線非常的大，否則值4應該很夠用。
                    gListenerSocket.Listen(gMaxListenIncomingConnections);

                    gListenerAcceptLoopFlag = true;
                    //FireSocketMsgEvent(LEVSocketEventType.ServerStart, null, "Server is Created : " + gLocalEndPoint.Address + ", Listening Port: " + gLocalEndPoint.Port);

                    while (gListenerAcceptLoopFlag)
                    {
                        //ManualResetEvent必須要再呼叫Reset方法後才會處於未收到信號的狀態
                        // Set the event to nonsignaled state.
                        SignalEvent.Reset();

                        //SocketInformLog("Server is starting listening and begining accept. ListenerSocket HashCode = " + gListenerSocket.GetHashCode() + Environment.NewLine);
                        // 以上完成了SERVER的Listening
                        // 之後BeginAccept()  開啟執行緒準備接收client的要求。
                        // 此處的BeginAccept 第一個參數就是當Socket一開始接收到Client的連線要求時，
                        // 立刻會呼叫delegate的AcceptCallback的方法，這個AcceptCallback名稱是我們自己取的
                        // Start an asynchronous socket to listen for connections.                                        
                        gListenerSocket.BeginAccept(new AsyncCallback(AcceptCallback), gListenerSocket);

                        //WaitOne的方法鎖定了執行緒，狀態也是WaitSleepJoin.
                        // Wait until a connection is made before continuing.
                        SignalEvent.WaitOne();
                    }

                }
                catch (Exception e)
                {
                    gListenerAcceptLoopFlag = false;
                    FireExceptionEvent(LEVSocketExceptionType.AcceptException, null,
                        "To start listening and to accept an incoming connection attempt failure." + Environment.NewLine + e);
                }
            }

            private void AcceptCallback(IAsyncResult ar)
            {
                Socket handlerSocket = null;

                try
                {
                    //喚醒多個執行緒,讓進入Waitting回到Running狀態。
                    // Signal the main thread to continue.
                    SignalEvent.Set();

                    //AsyncState 取得符合或包含非同步作業資訊的使用者定義的物件
                    // Get the socket that handles the client request.
                    Socket listener = (Socket)ar.AsyncState;  // 取得主要的socket, 其中包含使用者定義的物件及handles the client request
                    //將主要正在 listening(正在連入的client Socket)轉交給另一個臨時的handlerSocket變數，並且結束接受此一客戶端的連線

                    handlerSocket = listener.EndAccept(ar);
                    //取得遠端節點的EndPoint handler.RemoteEndPoint.ToString().Split(':')[0]

                    //SocketInformLog("A client has connected. client ip : " + handlerSocket.RemoteEP.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handlerSocket.GetHashCode() + Environment.NewLine);

                    gClientSocketList.Add(handlerSocket);
                    // Create the state object.
                    SocketStateObj state = new SocketStateObj();
                    state.workSocket = handlerSocket;

                    // 假設已連線的客戶端要傳送資料時，所指定的回呼函式-ReadCallback，這也是C#中的delagate型別
                    // 定義當Socket 接收到資料時要交給哪一個函數處理，這裡定義了BeginReceive()
                    // 參數第一個為存放的位置，為byte[]，傳入的資料會置於此陣列中；第二參數為啟始位置、第三參數為一次接收的最大長度；
                    // 第四為封包的旗標，例如標記為"廣播封包"之類，
                    // 第五參數為開始接受資料時叫用的回呼函式，用AsyncCallback實體化指定ReadCallback為回呼函式，所以當客戶端將資料傳進來時，ReadCallback函式會被叫用；
                    // 第六為狀態的參數，這個是用戶自訂，當回呼函式ReadCallback被叫用 時，此參數的值會被傳遞到ReadCallback，其中包含Socket，但是回呼函式仍會曉得是哪一個Socket在傳送資料。
                    handlerSocket.BeginReceive(state.buffer, 0, SocketStateObj.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), state);
                }
                catch (Exception e)
                {
                    FireExceptionEvent(LEVSocketExceptionType.ConnectException, handlerSocket,
                        "[AcceptCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket." + Environment.NewLine + e);
                }
            }

            private void ReadCallback(IAsyncResult ar)
            {
                String content = String.Empty;

                //重新取回 state object
                // Retrieve the state object and the handler socket from the asynchronous state object.
                SocketStateObj state = (SocketStateObj)ar.AsyncState;
                Socket handler = state.workSocket;

                try
                {
                    //SocketInformLog("Client is waitting and begining to receive data. client ip : " + handler.RemoteEndPoint.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handler.GetHashCode() + Environment.NewLine);

                    //叫用 EndReceive 完成指定的非同步接收作業, 參數asyn (ar) 識別要完成的非同步接收作業，並要從其中擷取最終結果。
                    // Read data from the client socket. 
                    int bytesRead = handler.EndReceive(ar);
                    //SocketInformLog("ReadCallback; byteRead = " + bytesRead);

                    if (bytesRead > 0)
                    {
                        #region msdn code
                        // There  might be more data, so store the data received so far.
                        //state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                        //state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead));

                        byte[] currentRead = new byte[bytesRead];
                        Array.Copy(state.buffer, currentRead, bytesRead);
                        state.listBuffer.AddRange(currentRead);
                        //FireReceivingEvent(SocketReceivingTyp.Fragment, handler, currentRead);

                        //content = state.sb.ToString();
                        //content = Encoding.UTF8.GetString(state.buffer, 0, bytesRead);

                        //only for compare, so use Encoding.ASCII
                        content = Encoding.ASCII.GetString(state.buffer, 0, bytesRead);
                        //SocketInformLog("Client is receiving data. client ip : " + handler.RemoteEndPoint.ToString().Split(':')[0] + ",  Client Socket HashCode = " + handler.GetHashCode() + Environment.NewLine);
                        //SocketInformLog("ReadCallback; content = " + content + Environment.NewLine);

                        // Check for end-of-file tag. If it is not there, read more data.
                        //if(LEVSocketUtilities.ByteArrayIndexOf(currentRead, 0, gOldVer_EOF_TokenBArray) > -1)
                        //if (content.IndexOf("<EOF>") > -1)
                        if (content.IndexOf(PACKET_EOF_TOKEN) > -1)
                        {
                            // All the data has been read from the 
                            // client. Display it on the console.
                            //Console.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content);
                            // Echo the data back to the client.
                            //Send(handler, content);
                            byte[] packet = state.listBuffer.ToArray<byte>();
                            int idx = LEVSocketUtilities.ByteArrayIndexOf(packet, Encoding.ASCII.GetBytes(_OldVer_EOF_Token));
                            if (idx > -1)
                            {
                                byte[] cpy = new byte[idx];
                                Array.Copy(packet, cpy, idx);
                                FireReceivingEvent(SocketReceivingTyp.OldVerWholePacket, handler, cpy);
                            }
                            //SocketInformLog("ReadCallback; Get <EOF> and Send back to client." + Environment.NewLine);
                            //Send_PreviousVer(handler, "get Data finish");
                            state.listBuffer.Clear();
                            //state.sb.Clear();
                        }
                        else if (LEVSocketUtilities.ByteArrayIndexOf(currentRead, SocketCmdToken._packetDataEOF_.ToString()) > -1)
                        {
                            byte[] packet = state.listBuffer.ToArray<byte>();
                            int idx = LEVSocketUtilities.ByteArrayIndexOf(packet, SocketCmdToken._packetDataEOF_.ToString());
                            if (idx > -1)
                            {
                                byte[] cpy = new byte[idx];
                                Array.Copy(packet, cpy, idx);
                                FireReceivingEvent(SocketReceivingTyp.WholePacket, handler, cpy);
                            }
                            //FireReceivingEvent(SocketReceivingTyp.WholePacket, handler, state.listBuffer.ToArray<byte>());
                            state.listBuffer.Clear();
                        }
                        else
                        {
                            //SocketInformLog("ReadCallback; Not all data received. Get more." + Environment.NewLine);
                            // Not all data received. Get more.
                        }
                        handler.BeginReceive(state.buffer, 0, SocketStateObj.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), state);
                        #endregion

                        #region modify code
                        // All data received. Get next message. 
                        // Not all data received. Get more.
                        //handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);        //new

                        //// There  might be more data, so store the data received so far.
                        ////state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                        //state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead));


                        //// Check for end-of-file tag. If it is not there, read more data.
                        //content = state.sb.ToString();

                        //////////////////////////////

                        //if (content.IndexOf("<EOF>") > -1)
                        //{
                        //    //// Reply to the client.
                        //    //Send(handler, resultData);

                        //    // All data received. Get next message. 
                        //    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);        //new

                        //}
                        //else
                        //{
                        //    // Not all data received. Get more.
                        //    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
                        //}
                        #endregion
                    }
                }
                catch (SocketException e)
                {
                    /////////////////////////////////////////////////////////////
                    //if (ex.ErrorCode == 10054 || ex.ErrorCode == 10053)
                    //{
                    //    log.Write(client_ip + " has left");
                    //    LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.ClientLefted,
                    //            client_ip, "Client has left");
                    //}
                    //else
                    //{
                    //    //Console.WriteLine(ex);
                    //    log.Write("ReadCallBack1: " + ex.Message);  //2013/8/22: Log exception message
                    //    LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                    //            "-", "ReadCallBack1: " + ex.Message);
                    //}

                    //SocketInformLog("[SocketException][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e + Environment.NewLine);
                    //throw new Exception("[SocketException] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e);
                    //SocketInformLog("handlerSocket Close. HashCode = " + handler.GetHashCode() + Environment.NewLine);
                    FireExceptionEvent(LEVSocketExceptionType.DisConnectException, handler, "[SocketException][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket." + Environment.NewLine + e);
                    closeClientSocket(handler, true, false);
                    FireSocketMsgEvent(LEVSocketEventType.ClientDisconnect, handler, "Client is going to be disconnected.");

                }
                catch (Exception e)
                {
                    //SocketInformLog("[Exception][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e + Environment.NewLine);
                    FireExceptionEvent(LEVSocketExceptionType.DisConnectException, handler, "[Exception][ReadCallback] An error occurred when attempting to access the socket or to receive data from a connected Socket." + Environment.NewLine + e);
                    //throw new Exception("[Exception] An error occurred when attempting to access the socket or to receive data from a connected Socket. ==> " + e);
                    //log.Write("ReadCallBack2: " + e.Message);   //2013/8/22: Log exception message
                    //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                    //        "-", "ReadCallBack2: " + e.Message);
                }
            }

            public void Send_PreviousVer(Socket handler, String data)
            {
                try
                {
                    //log.Write(handler.RemoteEndPoint.ToString().Split(':')[0] + " >> " + data);
                    //LogHelper.WriteServerDataLog(LogHelper.MsgType.Normal, LogHelper.Action.TransmittedToClient,
                    //        handler.RemoteEndPoint.ToString().Split(':')[0], data);

                    data += PACKET_EOF_TOKEN;
                    // Convert the string data to byte data using encoding.
                    byte[] byteData = Encoding.UTF8.GetBytes(data);

                    //SocketInformLog("Send back to Client = " + data + Environment.NewLine);

                    // Begin sending the data to the remote device.
                    handler.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(SendCallback), handler);
                }
                catch (Exception e)
                {
                    FireExceptionEvent(LEVSocketExceptionType.SendException, handler, "[Exception][Send] Sends data asynchronously to a connected Socket Failure." + Environment.NewLine + e);
                    //SocketInformLog("[Exception][Send] Sends data asynchronously to a connected Socket Failure. ==> " + e + Environment.NewLine);
                    //throw new Exception("[Exception] Sends data asynchronously to a connected Socket Failure. ==> " + e);
                    //log.Write("Send: " + e.Message);   //2013/8/22: Log exception message
                    //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                    //        "-", "Send: " + e.Message);
                }
            }

            private void SendCallback(IAsyncResult ar)
            {
                Socket handler = null;
                try
                {
                    // Retrieve the socket from the state object.
                    handler = (Socket)ar.AsyncState;
                    #region msdn
                    // Complete sending the data to the remote device.
                    int bytesSent = handler.EndSend(ar);
                    //Console.WriteLine("Sent {0} bytes to client.", bytesSent);
                    //Thread.Sleep(10000);
                    //SocketInformLog("SendCallback;  bytesSent = " + bytesSent + Environment.NewLine);


                    //handler.Shutdown(SocketShutdown.Both);
                    //handler.Close();
                    #endregion
                    #region modify code
                    // Complete sending the data to the remote device.
                    //int bytesSent = handler.EndSend(ar);
                    #endregion
                }
                catch (Exception e)
                {
                    FireExceptionEvent(LEVSocketExceptionType.SendException, handler, "[Exception][SendCallback] Sends data asynchronously to a connected Socket Failure." + Environment.NewLine + e);
                    //SocketInformLog("[Exception][SendCallback] Sends data asynchronously to a connected Socket Failure. ==> " + e + Environment.NewLine);
                    //throw new Exception("[Exception] Sends data asynchronously to a connected Socket (SendCallback) Failure. ==> " + e);
                    //log.Write("SendCallBack: " + e.Message);   //2013/8/22: Log exception message
                    //LogHelper.WriteServerDataLog(LogHelper.MsgType.Exception, LogHelper.Action.ServerMessages,
                    //        "-", "SendCallBack: " + e.Message);
                }
            }

            // The method which delegate the Event, If anyone has to be notified
            public void SetSocketMsgEventHandler(SocketMsgEventHandler handler)
            {
                msgEventHandler = handler;
            }
            // The method which fires the Event, If anyone has subscribed, notify them
            private void FireSocketMsgEvent(LEVSocketEventType eventTyp, Socket client, string Msg)
            {
                if (msgEventHandler != null)
                {
                    msgEventHandler(eventTyp, client, Msg);
                }
            }
            // The method which delegate the Event, If anyone has to be notified
            public void SetExceptionEventHandler(SocketExceptionEventHandler handler)
            {
                excptEventHandler = handler;
            }
            // The method which fires the Event, If anyone has subscribed, notify them
            private void FireExceptionEvent(LEVSocketExceptionType eventTyp, Socket client, string Msg)
            {
                if (excptEventHandler != null)
                {
                    excptEventHandler(eventTyp, client, Msg);
                }
            }
            // The method which delegate the Event, If anyone has to be notified
            public void SetReceivingEventHandler(ReceivingEventHandler handler)
            {
                receiveEventHandler = handler;
            }
            // The method which fires the Event, If anyone has subscribed, notify them
            private void FireReceivingEvent(SocketReceivingTyp receiveType, Socket client, byte[] receivingData)
            {
                if (receiveEventHandler != null)
                {
                    receiveEventHandler(receiveType, client, receivingData);
                }
            }
        }//public class ServerSocket
    }//namespace LEVNetSocket
}//namespace LEVNet
